Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 7 Weitere Möglichkeiten von C#
  gp 7.1 Operatorüberladung
    gp 7.1.1 Die Syntax der Operatorüberladung
    gp 7.1.2 Beispiel einer Operatorüberladung
    gp 7.1.3 Überladungsbeispiele
    gp 7.1.4 Benutzerdefinierte Konvertierungen – implizit und explizit
  gp 7.2 Indexer
    gp 7.2.1 Überladen von Indexern
    gp 7.2.2 Parameterbehaftete Eigenschaften
  gp 7.3 Collections (Auflistungen)
    gp 7.3.1 Die elementaren Schnittstellen der Auflistungsklassen
    gp 7.3.2 Die Klasse »ArrayList«
    gp 7.3.3 Das Sortieren der Elemente einer »ArrayList«
    gp 7.3.4 Die Schnittstelle »IDictionary«
    gp 7.3.5 Die Klasse »Hashtable«
    gp 7.3.6 Die Klassen »Queue« und »Stack«
    gp 7.3.7 Objektauflistungen im Überblick
    gp 7.3.8 Benutzerdefinierte Auflistungen
  gp 7.4 Generics – Generische Datentypen
    gp 7.4.1 Die Typproblematik am Beispiel der Klasse »Stack«
    gp 7.4.2 Die Lösung mit einer generischen Klasse
    gp 7.4.3 Typparameter mit Constraints einschränken
    gp 7.4.4 Generische Methoden
    gp 7.4.5 Generics und Vererbung
    gp 7.4.6 Konvertierung von Generics
    gp 7.4.7 Generische Delegate
    gp 7.4.8 Generische Klassen in der .NET-Klassenbibliothek
    gp 7.4.9 Eigene Auflistungen mit »yield« durchlaufen
    gp 7.4.10 Daten durch »null« beschreiben
  gp 7.5 Fortgeschrittene Delegat-Techniken
    gp 7.5.1 Multicast-Delegate
  gp 7.6 Attribute
    gp 7.6.1 Das »Flags«-Attribut
    gp 7.6.2 Anmerkungen zu den Attributen
    gp 7.6.3 Benutzerdefinierte Attribute
  gp 7.7 Unsicherer Programmcode – Zeigertechnik in C#
    gp 7.7.1 Das Schlüsselwort »unsafe«
    gp 7.7.2 Die Deklaration von Zeigern
    gp 7.7.3 Die »fixed«-Anweisung
    gp 7.7.4 Zeigerarithmetik
    gp 7.7.5 Der Operator »->«


Galileo Computing

7.5 Fortgeschrittene Delegat-Techniken  downtop

Stellen Sie sich vor, Sie hätten den Auftrag bekommen, eine Software zu entwickeln, die eine Pumpenanlage zum Befüllen des Schwimmbeckens eines Schwimmbades ansteuert. Es handelt sich bei dieser Anlage um Pumpen verschiedener Hersteller. Grundsätzlich sollen alle Pumpen eingeschaltet werden, wenn das Becken gefüllt wird. Anzahl und Typ der Pumpen können dabei durchaus variieren. Ihre Software soll so flexibel sein, sich an solche Änderungen automatisch anpassen zu können. Wie kann das Problem am besten gelöst werden?

Man kann davon ausgehen, dass jede Pumpe anders angesteuert werden muss. Daher bietet es sich an, für jede in Frage kommende Pumpe eine eigene Klasse mit einer Methode zu entwickeln, aus der heraus die Pumpe gestartet wird. Wir wollen zunächst zwei Klassen bereitstellen, PumpeA und PumpeB, deren Methoden SwitchOnA und SwitchOnB für die komplexen Einschaltvorgänge stehen sollen.


class PumpeA {
  public void SwitchOnA() {
    Console.WriteLine("Pumpe A wird eingeschaltet");
  }
}
class PumpeB {
  public void SwitchOnB() {
    Console.WriteLine("Pumpe B wird eingeschaltet");
  }
}

Eine Pumpenanlage, in der je eine Pumpe von jedem Typ installiert ist, soll nun eingeschaltet werden. Dazu implementieren wir eine Klasse, welche die Ansteuerung des Startvorgangs der Pumpen übernimmt. Eine weitere Klasse ist als Komponente in der Benutzeranwendung implementiert und ruft eine Methode in der pumpensteuernden Klasse auf, mit welcher der Startvorgang in Gang gesetzt wird.

    Wenden wir uns zunächst der Klasse zu, die eine Methode bereitstellen soll, aus der heraus die pumpenspezifischen Startmethoden aufgerufen werden. Im einfachsten Fall könnte der Code wie folgt lauten:
       

class ControlPumps {
  public void StartAllPumps() {
    PumpeA p1 = new PumpeA();
    PumpeB p2 = new PumpeB();
    p1.SwitchOnA();
    p2.SwitchOnB();
  }
}

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 7.3   Starten der Pumpen über eine steuernde Klasse

Ein Client könnte nun mit


ControlPumps obj = new ControlPumps();
obj.StartAllPumps();

zwar das Füllen des Beckens in die Wege leiten, aber dieser Ansatz hat einen ganz wesentlichen Nachteil: Ihm fehlt die Flexibilität, der Steuerung eine oder auch mehrere Pumpen dynamisch hinzufügen zu können und deren spezifische Startmethode aufzurufen. Die Klasse ControlPumps, die den Kern der gesamten Anwendung darstellt, müsste mit jeder neu installierten Pumpe ausgetauscht werden. Das gilt selbstverständlich auch, wenn eine Pumpe deinstalliert wird. Was ist außerdem, wenn ein Pumpenhersteller einen neuen Typ auf den Markt bringt, der für den Schwimmbadbetreiber vielleicht aufgrund der Leistungsdaten interessant ist? Die Ergänzung oder der Austausch der installierten Pumpen wäre nicht ohne Komplikationen – und das nur, weil unsere Steuerungssoftware kläglich versagt.

Wir haben es mit zwei Problemen zu tun, die gelöst werden müssen:

1.  das Starten einer beliebigen Anzahl von Pumpen aus der Methode StartAllPumps heraus
2. die Standardisierung des Aufrufs der spezifischen Startmethoden
       

Der zweite Punkt bringt uns sofort in Erinnerung, dass ein Delegat ein Funktionsprototyp ist und einen allgemeinen Methodenaufruf ermöglicht. Auch ohne bisher einen programmiertechnischen Ansatz entwickelt zu haben, wissen wir zumindest schon, welcher Weg uns zum Ziel führt.

Es fehlt im Wesentlichen nur noch eine Idee zur Lösung des ersten Punktes. Ein möglicher Lösungsansatz könnte ein Array sein, dessen Elemente die verschiedenen Pumpen referenzieren. Dieses Array könnte elementweise durchlaufen werden. Dazu bietet sich eine Collection an, beispielsweise ArrayList, die sich insbesondere durch einfache Programmierbarkeit auszeichnet.

Sehen wir uns den Code in der Klasse ControlPumps an, der unter Einbeziehung eines ArrayList-Objekts unsere Bedingungen erfüllt:


public delegate void PumpDelegate();
class ControlPumps {
  private ArrayList colPumps = new ArrayList();
  public void AddPump(PumpDelegate newPump) {
    colPumps.Add(newPump);
  }
  public void StartAllPumps(){
    foreach(PumpDelegate del in colPumps)
      del();
  }
}

Verantwortlich für das Verhalten der Klasse ist das Feld colPumps, das vom Typ der Collection ArrayList ist. Das Feld wird initialisiert, sobald ein Benutzer ControlPumps instanziiert. ColPumps dient aber nicht dazu, Pumpenobjekte zu verwalten. Diese spielen bei genauer Betrachtung eine eher untergeordnete Rolle, denn vielmehr sind die Methoden von Interesse, mit denen die Pumpen gestartet werden. Aus diesem Blickwinkel heraus drängt sich die Idee auf, vom Auflistungsobjekt Delegate verwalten zu lassen, die einen entsprechenden Zeiger auf die zu einer Pumpe gehörende Startmethode kapseln.

Die Methode, mit der eine Pumpe – oder präzise ausgedrückt deren Startmethode – zu dem Objekt colPumps hinzugefügt wird, lautet AddPump. AddPump empfängt vom Aufrufer im Parameter newPump einen Delegaten vom Typ PumpDelegate. Dessen Referenz wird unter Aufruf der Methode Add dem ArrayList-Objekt übergebeben. Die Methode StartAllPumps ist an Einfachheit kaum noch zu übertreffen. In einer foreach-Schleife wird jedes Element der Auflistung colPumps durchlaufen und ausgeführt, was in unserem Beispiel zur Ausführung einer spezifischen Pumpenstartmethode führt.

Widmen wir uns nun dem Client und betrachten den Code, mit dem ein Delegat, der bekanntermaßen die Startmethode eines bestimmten Pumpenobjekts beschreibt, an die AddPump-Methode übergeben wird:


ControlPumps obj = new ControlPumps();
PumpeA p1 = new PumpeA();
PumpDelegate pumpDel = new PumpDelegate(p1.SwitchOnA);
obj.AddPump(pumpDel);

Wir besorgen uns zuerst sowohl eine Instanz der Klasse ControlPumps als auch die einer Pumpe. Im Codefragment handelt es sich um eine Pumpe vom Typ PumpeA. Um die Pumpe zu starten, benötigt das ControlPumps-Objekt einen Delegaten auf die Startmethode der Pumpe. Dieser wird in der dritten Codezeile erzeugt und in der vierten der AddPump-Methode als Argument übergeben.

Weil im weiteren Verlauf die Referenz auf den Delegaten nicht mehr benötigt wird, können wir den Programmcode auch etwas kürzer schreiben, indem wir den Delegaten direkt beim Aufruf der AddPump-Methode erzeugen:


PumpeA p1 = new PumpeA();
obj.AddPump(new PumpDelegate(p1.SwitchOnA));

Nehmen wir an, dass vier Pumpen gestartet werden sollen, dann würde die Definition der Benutzerklasse wie folgt lauten:


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 7\SimpleDelegate 
// --------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    ControlPumps obj = new ControlPumps();
    // erste Pumpe 
    PumpeA p1 = new PumpeA();
    obj.AddPump(new PumpDelegate(p1.SwitchOnA));
    // zweite Pumpe
    PumpeB p2 = new PumpeB();
    obj.AddPump(new PumpDelegate(p2.SwitchOnB));
    // dritte Pumpe
    PumpeA p3 = new PumpeA();
    obj.AddPump(new PumpDelegate(p3.SwitchOnA));
    // vierte Pumpe
    PumpeA p4 = new PumpeA();
    obj.AddPump(new PumpDelegate(p4.SwitchOnA));
    // die Pumpen starten
    obj.StartAllPumps();
    Console.ReadLine();
  }
}

Die Ausgabe im Befehlsfenster lautet:


Pumpe A wird eingeschaltet.
Pumpe B wird eingeschaltet.
Pumpe A wird eingeschaltet.
Pumpe A wird eingeschaltet.

Das Ergebnis ist perfekt, wir haben das Ziel erreicht. Die Klasse ControlPumps ist so flexibel implementiert, dass sie nicht nur die Belange eines Schwimmbads abdeckt, sondern überall dort eingesetzt werden könnte, wo Pumpen der Reihe nach eingeschaltet werden müssen. Eigentlich ist diese Aussage falsch, denn wir können sie sogar auf jedwede beliebige Komponente ausdehnen, unter der Voraussetzung, dass in der Komponente eine parameterlose Methode aufgerufen werden soll. Die Verhaltensweise, die von der Methode beschrieben wird, spielt dabei keine Rolle – alles dank der Delegate.


Galileo Computing

7.5.1 Multicast-Delegate  toptop

.NET bietet die Möglichkeit, mehrere Delegate zu einem einzigen zusammenzufassen. Dadurch entsteht ein Delegaten-Verbund, der auch als Multicast-Delegat bezeichnet wird. Der Vorteil ist, dass durch den Aufruf eines Delegaten mehrere Delegate der Reihe ausgeführt werden können. Sehen Sie sich dazu noch einmal unser Beispiel SimpleDelegate des vorhergehenden Abschnitts an. Es wird darin von vier Pumpen ausgegangen, deren Startmethoden nacheinander von je einem Delegaten eingebunden werden. In der steuernden Klasse bedarf es eines Objekt-Arrays, um alle Delegate zu verwalten.

Der Programmcode ist wesentlich einfacher und übersichtlicher, wenn ein Multicast-Delegat die Aufgabe übernimmt. Dies soll das folgende Beispiel zeigen, das unter denselben Vorgaben wie das Beispiel SimpleDelegate entwickelt worden ist. Die Klassendefinitionen der Pumpen haben sich natürlich auch jetzt nicht verändert.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 7\MulticastDelegate 
// --------------------------------------------------------------
...
public delegate void PumpDelegate();
class Program {
  static void Main(string[] args) {
    ControlPumps obj = new ControlPumps();
    // Array vom Typ PumpDelegate
    PumpDelegate[] del = new PumpDelegate[4];
    // Pumpenobjekte erzeugen
    PumpeA p1 = new PumpeA();
    PumpeB p2 = new PumpeB();
    PumpeA p3 = new PumpeA();
    PumpeA p4 = new PumpeA();
    // die Startmethoden der Pumpen durch ein Delegate-
    // Objekt beschreiben
    del[0] = new PumpDelegate(p1.SwitchOnA);
    del[1] = new PumpDelegate(p2.SwitchOnB);
    del[2] = new PumpDelegate(p1.SwitchOnA);
    del[3] = new PumpDelegate(p1.SwitchOnA);
    // alle Delegate kombinieren
    PumpDelegate arrDel = (PumpDelegate)Delegate.Combine(del);
    // das Delegate-Array an die Steuerklasse übergeben
    obj.AddPump(arrDel);
    // die Pumpen starten
    obj.StartAllPumps();
    Console.ReadLine();
  }
}
// ------------- steuernde Klasse -------------
class ControlPumps {
  private PumpDelegate delPumps;
  public void AddPump(PumpDelegate pumps) {
    delPumps = pumps;
  }
  public void StartAllPumps() {
    delPumps();
  }
}
...

Werfen wir zuerst einen Blick auf den Code in Main. Es fällt als Erstes auf, dass die den Pumpenobjekten zugeordneten Delegate nun zu Elementen des Arrays del werden. Das hätten wir natürlich auch schon im Code des Beispiels SimpleDelegate so machen können. Nun steckt aber eine ganz bestimmte Absicht dahinter, die in der darauf folgenden Anweisung deutlich wird:


PumpDelegate arrDel = (PumpDelegate)Delegate.Combine(del);

Da Delegate wie alles in der .NET-Welt Objekte sind, wird dieser Typ durch eine eigene Klasse im Namespace System beschrieben: Delegate. Mit der statischen Methode Combine dieses Typs lassen sich mehrere Delegate miteinander verknüpfen. Combine ist wie folgt überladen:


public static Delegate Combine(Delegate[]);
public static Delegate Combine(Delegate del1, Delegate del2);

Sie können als Argument entweder ein Array vom Typ Delegate übergeben oder haben die Alternative, zwei Delegate miteinander zu verknüpfen. Der Rückgabewert ist in beiden Fällen ein Delegat, oder präziser, es handelt sich um einen Multicast-Delegat, für den es in der Klassenbibliothek eine eigene Typdefinition gibt, die von Delegate abgeleitet ist: MulticastDelegate. Der Rückgabewert wird nach einer expliziten Konvertierung dem benutzerdefinierten Delegaten zugewiesen:


PumpDelegate arrDel = (PumpDelegate)Delegate.Combine(del);

Wir haben nun eine Objektvariable namens arrDel, die einen Multicast-Delegat referenziert, der seinerseits vier Singlecast-Delegate kombiniert. Dem Objekt der Steuerklasse müssen wir jetzt nur noch die Referenz arrDel übergeben und können uns daher in der Klassendefinition von ControlPumps das aggregierte ArrayList-Objekt ersparen. Das hat auch zur Folge, dass die Methoden AddPump und StartAllPumps an die neue Situation angepasst werden müssen. Insgesamt reduziert sich der Code und wird dadurch deutlich einfacher.

Führen Sie das Programm aus, wird an der Konsole dieselbe Ausgabe erscheinen wie im Beispiel aus Abschnitt 7.5.1:


Pumpe A wird eingeschaltet.
Pumpe B wird eingeschaltet.
Pumpe A wird eingeschaltet.
Pumpe A wird eingeschaltet.

Methoden eines Multicast-Delegaten

Jeder Delegat steht für eine Liste von Methodenaufrufen, die durchlaufen wird, sobald der Delegat ausgeführt wird. Im Falle eines Singlecast-Delegaten enthält diese Liste nur ein Element, bei einem Multicast-Delegaten können es mehrere sein. Auf diese Aufrufliste können Sie mit der Methode GetInvocationList der Klasse Delegate bzw. MulticastDelegate zugreifen, der Rückgabewert ist ein Delegaten-Array.

In der Klasse Delegate ist diese Methode wie folgt definiert:


public virtual Delegate[] GetInvocationList();

Um eine Methode zu der Aufrufliste hinzuzufügen oder von ihr zu entfernen, definiert die Delegate-Klasse die beiden statischen Methoden Combine und Remove. Wir hatten in unserem Beispiel oben einen Multicast-Delegaten erzeugt, indem wir vier Singlecast-Delegate in ein Array zusammengefasst und als Argument der Combine-Methode übergeben haben. Die überladene Version dieser Methode wollen wir uns zusammen mit der Remove-Methode anschauen:


public static Delegate Combine(Delegate del1, Delegate del2);
public static Delegate Remove(Delegate source, Delegate value);

Beide Parameterlisten sind identisch und erwarten sowohl im ersten als auch im zweiten Argument die Referenz auf einen Delegaten. Dem ersten Parameter wird die Referenz auf den Delegaten übergeben, zu dessen Aufrufliste ein weiterer Delegat hinzugefügt bzw. im Fall der Remove-Methode entfernt werden soll. Der zweite Parameter beschreibt den hinzuzufügenden bzw. zu entfernenden Delegaten. Dazu ein kleines Beispiel:


PumpDelegate del1 = new PumpDelegate(p1.SwitchOnA);
PumpDelegate del2 = new PumpDelegate(p2.SwitchOnB);
del2 = Delegate.Combine(del2, del1);
...
del2 = Delegate.Remove(del2, del1);

In der dritten Codezeile wird der Delegat zu einem Multicast-Delegat, in der letzten wird diese Zuordnung wieder aufgehoben. Es ist möglich, im zweiten Argument einen Multicast-Delegaten anzugeben, letztendlich verkleinert sich dadurch allerdings nicht der Programmcode. Bei einer Kombination mehrerer Delegate ist daher die Variante mit der Übergabe eines Arrays vorzuziehen.

Interessiert der Name der von einem Delegaten gekapselten Methode, lässt sich das durch die schreibgeschützte Eigenschaft Method nebst weiteren Informationen in Erfahrung bringen. Der Aufruf dieser Eigenschaft liefert als Rückgabewert die Referenz auf ein Objekt vom Typ System.Reflection.MethodInfo, das die unterschiedlichsten Informationen zu einer Methode bereitstellt, beispielsweise über die Instanzeigenschaft Name den Namen der von einem Delegaten eingeschlossenen Methode.


Console.WriteLine(del1.Method().Name);

Die ebenfalls schreibgeschützte Eigenschaft Target liefert eine Referenz auf das Objekt, dessen Instanzmethode der aktuelle Delegat aufruft:


public object Target {get;}

Da der Rückgabewert Object ist, wird man in der Regel zuerst explizit konvertieren müssen:


PumpeA newPump = (PumpeA)del1.Target;

 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de